﻿using System.Text;
using Microsoft.Extensions.Logging;
using SkiaSharp;

namespace Devonline.AspNetCore;

/// <summary>
/// 图片信息服务
/// 目前已能给图片嵌入信息, 
/// </summary>
/// <param name="logger"></param>
/// <param name="httpSetting"></param>
public class ImageInformationService(ILogger<ImageInformationService> logger, HttpSetting httpSetting) : BaseImageService(logger, httpSetting), IImageInformationService
{
    private readonly ImageInformationOptions _informationOptions = httpSetting.ImageInformation!;

    /// <summary>
    /// 向照片添加信息
    /// </summary>
    /// <param name="fileName">照片文件路径</param>
    /// <returns></returns>
    public string AddInformation(string fileName) => AddInformation(fileName, _informationOptions.Information, _informationOptions.Flag, Encoding.GetEncoding(_informationOptions.Encoding), _informationOptions.SaveAs);
    /// <summary>
    /// 从照片中获取信息
    /// </summary>
    /// <param name="fileName">照片文件路径</param>
    /// <returns></returns>
    public string GetInformation(string fileName) => GetInformation(fileName, _informationOptions.Flag, Encoding.GetEncoding(_informationOptions.Encoding));

    /// <summary>
    /// 向照片添加信息
    /// </summary>
    /// <param name="fileName">照片文件路径</param>
    /// <param name="information">要添加的信息字符串</param>
    /// <param name="flag">标志位, 用于产生添加信息的信息头尾标志部分</param>
    /// <param name="encoding">信息部分的字符编码, 默认使用 utf8</param>
    /// <param name="saveAs">结果是否另存为, 另存为将保存为新文件</param>
    /// <returns></returns>
    private string AddInformation(string fileName, string information, string flag, Encoding encoding, bool saveAs)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException("原文件不存在!", fileName);
        }

        //加载原始图片到内存中
        using var stream = File.OpenRead(fileName);
        using var skManagedStream = new SKManagedStream(stream);
        using var original = SKBitmap.Decode(skManagedStream);

        // 向图片添加信息的过程
        //1. 得到原始图片的像素数据
        var pixels = original.Pixels;

        //2. 将信息字符串转换为 UTF8 编码的字节数组, 在转换为 二进制值
        var data = encoding.GetBytes(flag + information);
        var byteIndex = 0;
        byte current = 0;
        for (int index = 0; index < pixels.Length; index += UNIT_EIGHT)
        {
            if (index + UNIT_EIGHT >= pixels.Length)
            {
                break;
            }

            if (byteIndex >= data.Length)
            {
                // 如果已经处理完所有字节, 则回到 0 开始继续循环
                byteIndex = 0;
            }

            //3. 计算当前颜色通道的值嵌入到像素数据中
            current = data[byteIndex++];
            SetInfoIntoImageColorChannel(ref pixels, index, current);
        }

        //4. 将修改后的像素数据重新赋值给原始图片
        original.Pixels = pixels;
        stream.Close();

        //5. 保存修改后的图片到新文件
        var newFileName = saveAs ? GetNewFileName(fileName, "info") : fileName;
        logger.LogInformation($"{_userAccess}: add information '{information}' to image at {fileName}");
        return Save(original, newFileName);
    }
    /// <summary>
    /// 从图片中获取嵌入信息
    /// </summary>
    /// <param name="fileName"></param>
    /// <param name="flag"></param>
    /// <param name="encoding">信息部分的字符编码, 默认使用 utf8</param>
    /// <returns></returns>
    /// <exception cref="FileNotFoundException"></exception>
    private string GetInformation(string fileName, string flag, Encoding encoding)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException("原文件不存在!", fileName);
        }

        //加载原始图片到内存中
        using var stream = File.OpenRead(fileName);
        using var skManagedStream = new SKManagedStream(stream);
        using var original = SKBitmap.Decode(skManagedStream);

        // 从图片获取信息的过程
        //1. 得到原始图片的像素数据
        var pixels = original.Pixels;

        //2. 依次查找像素数据中的 Alpha 值的最后一位, 将其转换为二进制值
        var data = new List<byte>();
        var content = string.Empty;
        var evaluateLength = flag.Length * 5;
        byte current = 0;
        for (int index = 0; index < pixels.Length; index += UNIT_EIGHT)
        {
            if (index + UNIT_EIGHT >= pixels.Length)
            {
                break;
            }

            current = GetInfoFromImageColorChannel(ref pixels, index);
            data.Add(current);
            if (data.Count > evaluateLength)
            {
                //4. 计算当前数据是否已经包含了标志位的内容, 如果包含则说明已经获取到信息
                var info = encoding.GetString(data.ToArray());
                var indexOfFlag = info.IndexOf(flag);
                var lastIndexOfFlag = info.LastIndexOf(flag);
                if (indexOfFlag >= 0 && lastIndexOfFlag >= 0 && indexOfFlag != lastIndexOfFlag)
                {
                    //5. 截取标志位之间的字符串作为信息内容, 并退出计算
                    content = info[(indexOfFlag + flag.Length)..lastIndexOfFlag];
                    break;
                }

                //6. 每一轮无效计算后, 将数据清空, 重新开始计算
                if (indexOfFlag < 0 && lastIndexOfFlag < 0)
                {
                    data.Clear();
                }
            }
        }

        //7. 如果获取到信息内容, 则返回信息内容, 否则返回 null
        data.Clear();
        stream.Close();
        if (string.IsNullOrWhiteSpace(content))
        {
            logger.LogWarning($"{_userAccess}: can not get information from image file {fileName}");
        }
        else
        {
            logger.LogInformation($"{_userAccess}: success to get information: {content} from image file {fileName}");
        }

        return content;
    }

    /// <summary>
    /// 根据序号获取颜色通道值
    /// </summary>
    /// <param name="color">当前颜色</param>
    /// <param name="index">序号</param>
    /// <returns></returns>
    private static byte GetColorChannel(SKColor color, int index) => (index % 3) switch
    {
        0 => color.Red,
        1 => color.Green,
        2 => color.Blue,
        _ => color.Alpha
    };
    /// <summary>
    /// 根据序号和颜色值获取通道颜色
    /// </summary>
    /// <param name="color">当前颜色</param>
    /// <param name="index">序号</param>
    /// <param name="value">颜色值</param>
    /// <returns></returns>
    private static SKColor GetChannelColor(SKColor color, int index, byte value) => (index % 3) switch
    {
        0 => color.WithRed(value),
        1 => color.WithGreen(value),
        2 => color.WithBlue(value),
        _ => color.WithAlpha(value)
    };
    /// <summary>
    /// 根据序号和颜色值设置颜色通道值
    /// </summary>
    /// <param name="color"></param>
    /// <param name="index"></param>
    /// <param name="info"></param>
    /// <param name="offset"></param>
    private static SKColor GetChannelColor(SKColor color, int index, byte info, int offset)
    {
        var data = (offset == 0 ? info : (info >> offset)) & 0x01;
        var channelColor = (GetColorChannel(color, index) & 0xFE) | data;
        var result = GetChannelColor(color, index, (byte)channelColor);
        return result;
    }
    /// <summary>
    /// 将一个字节信息内容按顺序写入7个像素序列中
    /// </summary>
    /// <param name="pixels">像素序列</param>
    /// <param name="index">写入序号</param>
    /// <param name="info">当前要写入的字节信息</param>
    private static void SetInfoIntoImageColorChannel(ref SKColor[] pixels, int index, byte info)
    {
        var color = pixels[index];
        pixels[index] = GetChannelColor(color, index, info, 0);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 1);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 2);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 3);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 4);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 5);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 6);
        color = pixels[++index];
        pixels[index] = GetChannelColor(color, index, info, 7);
    }
    /// <summary>
    /// 根据序号和颜色通道值, 获取嵌入位信息
    /// </summary>
    /// <param name="color"></param>
    /// <param name="index"></param>
    /// <param name="offset"></param>
    /// <returns></returns>
    private static byte GetColorChannel(SKColor color, int index, int offset)
    {
        var colorChannel = GetColorChannel(color, index);
        var colorBit = colorChannel & 0x01;
        var result = offset == 0 ? colorBit : (colorBit << offset);
        return (byte)result;
    }
    /// <summary>
    /// 根据序号和颜色通道, 按顺序获取一个字节嵌入西悉尼
    /// </summary>
    /// <param name="pixels"></param>
    /// <param name="index"></param>
    /// <returns></returns>
    private static byte GetInfoFromImageColorChannel(ref SKColor[] pixels, int index)
    {
        //3. 获取当前颜色通道嵌入的数据值
        var color = pixels[index];
        var b0 = GetColorChannel(color, index, 0);
        color = pixels[++index];
        var b1 = GetColorChannel(color, index, 1);
        color = pixels[++index];
        var b2 = GetColorChannel(color, index, 2);
        color = pixels[++index];
        var b3 = GetColorChannel(color, index, 3);
        color = pixels[++index];
        var b4 = GetColorChannel(color, index, 4);
        color = pixels[++index];
        var b5 = GetColorChannel(color, index, 5);
        color = pixels[++index];
        var b6 = GetColorChannel(color, index, 6);
        color = pixels[++index];
        var b7 = GetColorChannel(color, index, 7);

        return (byte)(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
    }
}